/*
 * FileChooserField.java
 *
 *  created: 10.10.2011
 *  charset: UTF-8
 *  license: MIT (X11) (See LICENSE file for full license)
 */
package cz.mp.k3bg.gui.component;

import cz.mp.k3bg.Images;
import cz.mp.k3bg.gui.MainFrame;
import cz.mp.k3bg.gui.helper.FileChooserBuilder;
import cz.mp.k3bg.log.LoggerManager;
import cz.mp.k3bg.misc.DirectoryRestrictedFileSystemView;
import cz.mp.k3bg.misc.ExtFileFilter;
import cz.mp.k3bg.util.DesktopUtils;
import cz.mp.util.FileUtils;
import cz.mp.util.GuiUtils;
import cz.mp.util.StringUtils;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.io.File;
import java.util.logging.Logger;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.EventListenerList;
import javax.swing.filechooser.FileFilter;
import net.miginfocom.swing.MigLayout;

/**
 * Komponenta {@code FileChooserField} pro pohodlnější výběr souboru.
 * <p>
 * Komponenta je složena ze tří částí: 
 * <ol>
 * <li>tlačítko, které otevře java dialog pro výběr souboru,
 * </li><li>textové pole pro zobrazení vybraného souboru a pro zadání souboru ručně,
 * </li><li>tlačítko, které otevře operačním systémem přidružený program vybraného
 * souboru.
 * </li></ol>
 * <p>
 * Lze zadat kořenový adresář. Pak se budou v textovém poli zobrazovat 
 * cesty k souborům vzhledem ke kořenovému adresáři ("ralativní cesty").
 * 
 * @author Martin Pokorný
 * @version 0.3
 * @see FileChooserBuilder
 * @see DirectoryRestrictedFileSystemView
 */
public class FileChooserField extends JPanel {

    private static final boolean DEBUG = false;
    private static final Logger logger =
            LoggerManager.getLogger(FileChooserField.class, DEBUG);   
    
    private static final JButton tempButton = new JButton("...");
    
    private JButton fileSearchButton = new JButton("", Images.getImage(Images.FOLDER)) {
        @Override
        public Dimension getPreferredSize() {
            return new Dimension(
                    super.getPreferredSize().width,
                    tempButton.getPreferredSize().height);
        }
    };

    private JButton openFileButton = new JButton("", Images.getImage(Images.DETAIL));
    
    private boolean openButtonVisible = true;

    private JTextField fileField = new JTextField() {
        @Override
        public void setText(String text) {
            super.setText(text);
            moveCaretToEnd();
        }

        /**
         * 
         */
        private void moveCaretToEnd() {
            String text = getText();
            if (! StringUtils.isEmpty(text)) {
                setCaretPosition(text.length());
            }            
        }        
    };
        
    private FileChooserBuilder fileChooserBuilder = new FileChooserBuilder();
    
    public static final String DEFAULT_DIR = 
            System.getProperty("user.home");
        
    // -----

    /** */
    public FileChooserField() {
        this(DEFAULT_DIR, true);
    }
    
    /**
     *
     * @param openButtonVisible
     */
    public FileChooserField(boolean openButtonVisible) {
        this(DEFAULT_DIR, openButtonVisible);
    }
    
    /**
     *
     * @param selectedFilePath
     */
    public FileChooserField(String selectedFilePath) {
        this(selectedFilePath, true);
    }
    
    /**
     * 
     * @param selectedFilePath
     * @param openButtonVisible 
     */
    public FileChooserField(String selectedFilePath, boolean openButtonVisible) {
        setOpenButtonVisibleImpl(openButtonVisible);
        setSelectedFileImpl(selectedFilePath);
        
        initComponents();
        initLayout();        
        initEventHandlers();
    }
    
    // -----
    
    /**
     * 
     */
    private void initComponents() {
        fileChooserBuilder.setSelectionMode(JFileChooser.FILES_ONLY);
    }

    /** 
     * 
     */
    private void initLayout() {
        this.setLayout(new MigLayout("",
                "0[fill,grow,120:pref:max]rel[]0",
                "0[]0"));
        this.add(fileField, "sgy");
        this.add(fileSearchButton, "hidemode 3, sgy");
        if (openButtonVisible) {
            this.add(openFileButton, "hidemode 0, sgy");            
            
            openFileButton.setVisible(false);
        }        
    }
    
    /**
     * 
     */
    private void initEventHandlers() {
        fileField.addKeyListener(new KeyAdapter() {
            String oldValue = "";
            
			@Override
			public void keyReleased(KeyEvent e) {
                if (e.getKeyCode() == KeyEvent.VK_F3) {
                    openFile();
                }                
                else {
                    if (! (e.getSource() instanceof JTextField)) {
                        return;
                    }
                    JTextField textField = (JTextField)e.getSource();
                    String value = textField.getText().trim();
                    if (! oldValue.equals(value)) {
                        logger.finest("(fileField) oldValue = " + oldValue);
                        
                        oldValue = value;

                        logger.finest("(fileField) value    = " + value);
                        logger.finest("---");

                        handleChange();
                    }
                }
			}
		});
        
		fileSearchButton.addActionListener(new ActionListener() {
            @Override
			public void actionPerformed(ActionEvent e) {
                logger.finer("(fileSearchButton)");
                
                JFileChooser fileChooser = fileChooserBuilder.getFileChooser();
                String rootDirPath = fileChooserBuilder.getRoot();
                        
                File actualFile = new File(fileField.getText());
                if (rootDirPath != null) {
                    actualFile = new File(rootDirPath);                    
                }
				if (actualFile.isDirectory()) {
					fileChooser.setCurrentDirectory(actualFile);
				}
				else {
					fileChooser.setSelectedFile(actualFile);
				}

				int returnVal = fileChooser.showOpenDialog(
                        MainFrame.getInstance());
				if (returnVal != JFileChooser.APPROVE_OPTION)  {
					return;
				}

                setSelectedFileImpl(
                        FileUtils.getAbsoluteFilePath(
                            fileChooser.getSelectedFile()));

                handleChange();
			}
		});
        
        openFileButton.addActionListener(new ActionListener() {
            @Override
			public void actionPerformed(ActionEvent e) {
                logger.finer("(openFileButton)");
                
                openFile();                
            }
        });        
    }    
    
    /**
     * 
     */
    private void handleChange() {
		SwingUtilities.invokeLater(
            new Runnable() {
                @Override
                public void run() {
                    logger.finer("(change)");
                            
                    openFileButton.setVisible(
                            openButtonVisible
                            && isSelectedFile());
                    fireChangeEvent();
                }
            });
    }

    /**
     * Zjistí zda je v textovém poli zadán platný soubor.
     * 
     * @return 
     */
    public boolean isSelectedFile() {
        String filePath = getSelectedFileAbsolutePath();
        
        if (StringUtils.isBlank(filePath)) {
            return false;
        }
        File file = new File(filePath);
        if (!file.exists()) {
            return false;
        }
        
        // (pokud se zadá soubor se špatnou příponou do textového pole... 
        //  vrátí false)
        boolean acceptedFile = true;
        JFileChooser fileChooser = fileChooserBuilder.getFileChooser();
        FileFilter ffilter = fileChooser.getFileFilter();
        if (ffilter != null && 
                ! ffilter.equals(fileChooser.getAcceptAllFileFilter())) {
            acceptedFile = ffilter.accept(getSelectedFile());
        }
        
        if (fileChooser.getFileSelectionMode() == 
                JFileChooser.DIRECTORIES_ONLY) {            
            return acceptedFile && file.isDirectory();
        }
        if (fileChooser.getFileSelectionMode() == 
                JFileChooser.FILES_ONLY) {
            return acceptedFile && !file.isDirectory();
        }        
        return true;
    } 

    
	/**
	 * Nastaví zadanou cestu jako vybranou.
	 *
     * @param  path
	 * @return
     * @throws IllegalArgumentException
	 */
	public void setSelectedFile(String path) {
		setSelectedFileImpl(path);
	}

	/**
	 * Nastaví zadanou cestu jako vybranou.
	 *
     * @param  path
	 * @return
     * @throws IllegalArgumentException
	 */
	private void setSelectedFileImpl(String path) {
        if (StringUtils.isBlank(path)) {
            fileField.setText("");
        }
        if (fileChooserBuilder.getRoot() != null) {
            String relativePath = FileUtils.getAbsoluteFilePath(path)
                    .replace(fileChooserBuilder.getRoot() + File.separator, "");            
            fileField.setText(relativePath);
        }
        else {
            fileField.setText(
                    FileUtils.getAbsoluteFilePath(path));            
        }

        handleChange();
	}

    /**
     *
     * @return  prázdný řetězec, pokud ...
     */
    public String getSelectedFileAbsolutePath() {
        String path = getSelectedFilePath();
        if (StringUtils.isBlank(path)) {
			return "";
		}        
        if (fileChooserBuilder.getRoot() == null || 
                path.startsWith(fileChooserBuilder.getRoot())) {
            return path;
        }
        else {
            return fileChooserBuilder.getRoot() + File.separator + path;
        }
    }

	/**
	 * Získá cestu vybraného souboru.
	 *
	 * @return prázdný řetězec, pro případ, kdy není nic zadáno.
	 */
	public String getSelectedFilePath() {
		String path = fileField.getText();
        if (StringUtils.isBlank(path)) {
			return "";
		}
        return path;        
	}

    /**
     * Zjistí zda je v textovém poli zadána absolutní cesta.
     *
     * @return
     */
    boolean isSelectedAbsolutePath() {
        String filePath = getSelectedFilePath();
        if (filePath.startsWith(fileChooserBuilder.getRoot())) {
            return true;
        }
        return false;
    }

	/**
	 * Získá vybraný souboru.
	 *
	 * @return {@code null} pokud není zadána cesta k souboru.
	 */
	public File getSelectedFile() {
		String absPath = getSelectedFileAbsolutePath();
        if (StringUtils.isBlank(absPath)) {
			return null;
		}
        else {
            return new File(absPath);
        }
	}


    /**
     *
     * @param selectionMode  {@code JFileChooser.DIRECTORIES_ONLY}
     *      nebo {@code JFileChooser.FILES_ONLY}
     *      nebo {@code JFileChooser.FILES_AND_DIRECTORIES}
     */
    public void setSelectionMode(int selectionMode) {
        fileChooserBuilder.setSelectionMode(selectionMode);
    }


    /**
     * 
     * @param openButtonVisible 
     */
    private void setOpenButtonVisibleImpl(boolean openButtonVisible) {
        this.openButtonVisible = openButtonVisible;
        openFileButton.setVisible(openButtonVisible);
    }    


	/**
	 * Nastaví filtr definovaný seznamem přípon souboru a popisem filtru.
	 * 
	 * @param baseDescription
	 * @param exts
	 */
	public void setFilter(String baseDescription, String... exts) {
		ExtFileFilter filter = new ExtFileFilter(baseDescription, exts);
        setFilter(filter);                
	}
    
    /**
     * 
     * @param filter 
     */
    public void setFilter(ExtFileFilter filter) {
        fileChooserBuilder.setFilter(filter);
    }

    /**
     *
     */
	public void clear() {
		SwingUtilities.invokeLater(
            new Runnable() {
                @Override
                public void run() {        
                    fileField.setText("");
                    openFileButton.setVisible(false);
                    fireChangeEvent();
                }
            });        
	}
    
	/**
	 *
	 * @return
	 */
	public boolean isEmpty() {
        return fileField.getText().isEmpty();        
	}    
    
    /**
     * 
     * @return
     */
    public int getSelectionMode() {
        return fileChooserBuilder.getFileChooser().getFileSelectionMode();
    }
    
	/**
	 *
	 * @param editable
	 */
	public void setEditable(boolean editable) {
		fileField.setEditable(editable);
		fileSearchButton.setVisible(editable);
	}
    
	/**
	 *
	 * @return
	 */
	public boolean isEditable() {
		return fileField.isEditable();
	}
        
    /**
     * 
     * @param text 
     */
    public void setText(String text) {
        fileField.setText(text);
    }
            
    
    /**
     * Nastaví kořenový adresář.
     * 
     * @param rootDirPath 
     */
    public void setRoot(String rootDirPath) {
        clear();
        fileChooserBuilder.setRoot(rootDirPath);
        handleChange();        
    }

    /**
     * 
     * @return 
     */
    public String getRoot() {
        return fileChooserBuilder.getRoot();
    }
    
    /**
     * 
     */
    private void openFile() {
        String filePath = getSelectedFileAbsolutePath();
        if (filePath != null && openFileButton.isVisible()) {
            GuiUtils.setWaitCursorForAmoment(MainFrame.getInstance());
            DesktopUtils.openFile(filePath);
        }        
    }


    private boolean enabledInternal = true;

    @Override
    public void setEnabled(boolean enabled) {
        setOpaque(enabled);
        fileField.setEnabled(enabled);
        fileSearchButton.setEnabled(enabled);
        openFileButton.setEnabled(enabled);
        enabledInternal = enabled;
    }

    @Override
    public boolean isEnabled() {
        return enabledInternal;
    }

	// ----- ChangeEvent

	private EventListenerList changeListeners = new EventListenerList();

	/**
	 * Zaregistruje posluchače změny -- {@code ChangeEvent}.
	 * <em>poznámka:</em> událost {@code ChangeEvent} má být vystřelena při
	 * změně textu v poli s cestou k souboru nebo adresáři.
	 */
	public void addChangeListener(ChangeListener listener) {
		changeListeners.add(ChangeListener.class, listener);
	}

	public void removeChangeListener(ChangeListener listener) {
		changeListeners.remove(ChangeListener.class, listener);
	}

	private ChangeEvent changeEvent = new ChangeEvent(this);

    
	void fireChangeEvent() {
		fireChangeEvent(changeEvent);
	}

	/**
	 * Událost má být vystřelena při změně textu v poli s cestou k souboru.
	 *
	 * @param ed  (nemělo by být {@code null}!)
	 */
	private void fireChangeEvent(ChangeEvent ed) {
		Object[] listeners = changeListeners.getListenerList();
		for (int i = listeners.length - 2; i >= 0; i -= 2) {
			if (listeners[i] == ChangeListener.class) {
				((ChangeListener) listeners[i + 1]).stateChanged(ed);
			}
		}
	}    

}   // FileChooserField
